// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>
#include <stdint.h>

#include <sstream>
#include <string>
#include <vector>

#include "base/bind.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "media/base/media_util.h"
#include "media/base/stream_parser_buffer.h"
#include "media/base/timestamp_constants.h"
#include "media/base/video_decoder_config.h"
#include "media/formats/mp2t/es_adapter_video.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace media {
namespace mp2t {

    typedef StreamParser::BufferQueue BufferQueue;

    namespace {

        VideoDecoderConfig CreateFakeVideoConfig()
        {
            gfx::Size coded_size(320, 240);
            gfx::Rect visible_rect(0, 0, 320, 240);
            gfx::Size natural_size(320, 240);
            return VideoDecoderConfig(kCodecH264, H264PROFILE_MAIN, PIXEL_FORMAT_I420,
                COLOR_SPACE_UNSPECIFIED, coded_size, visible_rect,
                natural_size, EmptyExtraData(), Unencrypted());
        }

        BufferQueue GenerateFakeBuffers(const int* frame_pts_ms,
            const bool* is_key_frame,
            size_t frame_count)
        {
            uint8_t dummy_buffer[] = { 0, 0, 0, 0 };

            BufferQueue buffers(frame_count);
            for (size_t k = 0; k < frame_count; k++) {
                buffers[k] = StreamParserBuffer::CopyFrom(
                    dummy_buffer, arraysize(dummy_buffer),
                    is_key_frame[k], DemuxerStream::VIDEO, 0);
                if (frame_pts_ms[k] < 0) {
                    buffers[k]->set_timestamp(kNoTimestamp);
                } else {
                    buffers[k]->set_timestamp(
                        base::TimeDelta::FromMilliseconds(frame_pts_ms[k]));
                }
            }
            return buffers;
        }

    }

    class EsAdapterVideoTest : public testing::Test {
    public:
        EsAdapterVideoTest();

    protected:
        // Feed the ES adapter with the buffers from |buffer_queue|.
        // Return the durations computed by the ES adapter as well as
        // whether each frame emitted by the adapter is a key frame.
        std::string RunAdapterTest(const BufferQueue& buffer_queue);

    private:
        void OnNewConfig(const VideoDecoderConfig& video_config);
        void OnNewBuffer(scoped_refptr<StreamParserBuffer> buffer);

        EsAdapterVideo es_adapter_;

        std::stringstream buffer_descriptors_;

        DISALLOW_COPY_AND_ASSIGN(EsAdapterVideoTest);
    };

    EsAdapterVideoTest::EsAdapterVideoTest()
        : es_adapter_(base::Bind(&EsAdapterVideoTest::OnNewConfig,
                          base::Unretained(this)),
            base::Bind(&EsAdapterVideoTest::OnNewBuffer,
                base::Unretained(this)))
    {
    }

    void EsAdapterVideoTest::OnNewConfig(const VideoDecoderConfig& video_config)
    {
    }

    void EsAdapterVideoTest::OnNewBuffer(
        scoped_refptr<StreamParserBuffer> buffer)
    {
        buffer_descriptors_ << "(" << buffer->duration().InMilliseconds() << ","
                            << (buffer->is_key_frame() ? "Y" : "N") << ") ";
    }

    std::string EsAdapterVideoTest::RunAdapterTest(
        const BufferQueue& buffer_queue)
    {
        buffer_descriptors_.clear();

        es_adapter_.OnConfigChanged(CreateFakeVideoConfig());
        for (BufferQueue::const_iterator it = buffer_queue.begin();
             it != buffer_queue.end(); ++it) {
            es_adapter_.OnNewBuffer(*it);
        }
        es_adapter_.Flush();

        std::string s = buffer_descriptors_.str();
        base::TrimWhitespaceASCII(s, base::TRIM_ALL, &s);
        return s;
    }

    TEST_F(EsAdapterVideoTest, FrameDurationSimpleGop)
    {
        // PTS for a GOP without B frames - strictly increasing.
        int pts_ms[] = { 30, 31, 33, 36, 40, 45, 51, 58 };
        bool is_key_frame[] = {
            true, false, false, false,
            false, false, false, false
        };
        BufferQueue buffer_queue = GenerateFakeBuffers(pts_ms, is_key_frame, arraysize(pts_ms));

        EXPECT_EQ("(1,Y) (2,N) (3,N) (4,N) (5,N) (6,N) (7,N) (7,N)",
            RunAdapterTest(buffer_queue));
    }

    TEST_F(EsAdapterVideoTest, FrameDurationComplexGop)
    {
        // PTS for a GOP with B frames.
        int pts_ms[] = { 30, 120, 60, 90, 210, 150, 180, 300, 240, 270 };
        bool is_key_frame[] = {
            true, false, false, false, false,
            false, false, false, false, false
        };
        BufferQueue buffer_queue = GenerateFakeBuffers(pts_ms, is_key_frame, arraysize(pts_ms));

        EXPECT_EQ("(30,Y) (30,N) (30,N) (30,N) (30,N) "
                  "(30,N) (30,N) (30,N) (30,N) (30,N)",
            RunAdapterTest(buffer_queue));
    }

    TEST_F(EsAdapterVideoTest, LeadingNonKeyFrames)
    {
        int pts_ms[] = { 30, 40, 50, 120, 150, 180 };
        bool is_key_frame[] = { false, false, false, true, false, false };
        BufferQueue buffer_queue = GenerateFakeBuffers(pts_ms, is_key_frame, arraysize(pts_ms));

        EXPECT_EQ("(30,Y) (30,Y) (30,Y) (30,Y) (30,N) (30,N)",
            RunAdapterTest(buffer_queue));
    }

    TEST_F(EsAdapterVideoTest, LeadingKeyFrameWithNoTimestamp)
    {
        int pts_ms[] = { -1, 40, 50, 120, 150, 180 };
        bool is_key_frame[] = { true, false, false, true, false, false };
        BufferQueue buffer_queue = GenerateFakeBuffers(pts_ms, is_key_frame, arraysize(pts_ms));

        EXPECT_EQ("(40,Y) (40,Y) (30,Y) (30,N) (30,N)",
            RunAdapterTest(buffer_queue));
    }

    TEST_F(EsAdapterVideoTest, LeadingFramesWithNoTimestamp)
    {
        int pts_ms[] = { -1, -1, 50, 120, 150, 180 };
        bool is_key_frame[] = { false, false, false, true, false, false };
        BufferQueue buffer_queue = GenerateFakeBuffers(pts_ms, is_key_frame, arraysize(pts_ms));

        EXPECT_EQ("(70,Y) (30,Y) (30,N) (30,N)",
            RunAdapterTest(buffer_queue));
    }

} // namespace mp2t
} // namespace media
